package while_lang.semantics;

import java.util.*;

import while_lang.semantics.types.IntegerType;

/** Representación de una tabla de símbolos, utilizada en el chequeo de tipos 
 *  del lenguaje While extendido.
 *  @author Leonardo Val.
 */
public class SymbolTable {
	public final Map<String, Declaration> declarations = new HashMap<String, Declaration>();
	public final List<String> errors = new ArrayList<String>();
	
	/** El constructor de State permite armar estados con variables inicialmente 
	 * 	de manera más simple.
	 *  Por ejemplo: new State(new Variable("x", IntegerType.SINGLETON)); 
	 */
	public SymbolTable(Declaration... init) {
		for (int i = 0; i + 1 < init.length; i += 2) {
			declare(init[i]);
		}
	}
	
	/** Obtiene la declaración de una variable por identificador.
	 */
	public Declaration variable(String id) {
		if (declarations.containsKey(id)) {
			return declarations.get(id);
		} else {
			//throw new Error("La variable "+ id +" no está declarada.");
			addError("La variable "+ id +" no está declarada.");
			return declare(new Declaration(id, IntegerType.TYPE));
		}		
	}
	
	/** Agrega una declaración a la tabla de símbolos.
	 */
	public Declaration declare(Declaration decl) {
		if (declarations.containsKey(decl.id)) {
			//throw new Error("La variable "+ decl.id +" ya está declarada.");
			addError("La variable "+ decl.id +" ya está declarada.");
		} else {
			declarations.put(decl.id, decl);
		}
		return decl;
	}
	
	public String toString() {
		int length = declarations.size();
		String[] ids = declarations.keySet().toArray(new String[length]);
		Arrays.sort(ids);
		StringBuilder buffer = new StringBuilder("{ ");
		if (length > 0) {
			buffer.append(variable(ids[0]));
			for (int i = 1; i < length; i++) {
				buffer.append(", ").append(variable(ids[i]));
			}
		}
		return buffer.append(" }").toString();
	}
	
	/** Agrega un error a la lista de errores de la tabla.
	 */
	public int addError(String message) {
		errors.add(message);
		return errors.size();
	}
	
	/** Construye una nueva tabla de símbolos con las mismas declaraciones que
	 *  ésta.
	 */
	public SymbolTable clone() {
		SymbolTable table = new SymbolTable();
		for (Declaration decl : declarations.values()) {
			table.declare(new Declaration(decl.id, decl.type, decl.hasValue));
		}
		return table;
	}
	
	/** Actualiza esta tabla de símbolos con la intersección de las 
	 *  declaraciones de las tablas pasadas por parámetro. Agrega además todos
	 *  los errores.
	 */
	public void update(SymbolTable... tables) {
		Map<String, Declaration> decls = new HashMap<String, Declaration>();
		SymbolTable table = tables[0];
		decls.putAll(table.declarations);
		errors.addAll(table.errors);
		for (int i = 1; i < tables.length; i++) {
			table = tables[i];
			for (Declaration decl : decls.values()) {
				if (!table.declarations.containsKey(decl.id)
						|| decls.get(decl.id).type != decl.type) {
					decls.remove(decl.id);
				}
			}
			errors.addAll(table.errors);
		}
		declarations.clear();
		declarations.putAll(decls);
	}
	
}
